<template>
  <codemirror
    class="steve-editor"
    v-model="editorValue"
    :style="editorStyle"
    :disabled="readonly"
    :extensions="getExtensions"
    :placeholder="placeholder"
    :indent-with-tab="false"
    @ready="handleReady"
    @change="onChange"
  />
</template>

<script setup lang="ts">
import { computed, CSSProperties, ref, shallowRef, toRefs, watch } from 'vue';

import { Codemirror } from 'vue-codemirror';
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { html } from '@codemirror/lang-html';
import { oneDark } from '@codemirror/theme-one-dark';
import { EditorView } from '@codemirror/view';
import { jsAutoCode, stringAutoCode } from '@/config/code';
import { autocompletion } from '@codemirror/autocomplete';
import { closeSearchPanel, openSearchPanel, search } from '@codemirror/search';

// props
const props = defineProps({
  // fromData
  value: {
    type: String,
    default: ''
  },
  // 是否黑暗模式
  dark: {
    type: Boolean,
    default: false
  },
  // 组件高度
  height: {
    type: [Number, String],
    default: '100vh'
  },
  // 语言
  lang: {
    type: String,
    default: 'javascript'
  },
  // 是否只读
  readonly: {
    type: Boolean,
    default: false
  },
  // 提示
  placeholder: String,
  // 换行
  wrap: {
    type: Boolean,
    default: true
  },
  // 字体大小
  fontSize: {
    type: Number,
    default: 16
  },
  // 打开搜索面板
  searchPanel: {
    type: Boolean,
    default: false
  },
  // 滚动位置
  scrollTo: {
    type: [String, Number],
    default: ''
  }
});
const { value, dark, height, lang, wrap, fontSize, searchPanel, scrollTo } = toRefs(props);
const emit = defineEmits(['update:value', 'ready', 'change']);

// 编辑器ref
const view = shallowRef();
const handleReady = (payload: any) => {
  view.value = payload.view;
  emit('ready', view.value);
};

// 编辑器的参数
const getExtensions = computed(() => {
  const extensions = [
    search({
      top: false
    })
  ];
  // 设置语言
  if (editorLang.value && editorLang.value !== 'string') {
    extensions.push(editorLang.value);
    // 自定义提示
    if (lang.value === 'javascript') {
      extensions.push(javascriptLanguage.data.of({
        autocomplete: jsAutoCode
      }));
    }
  } else {
    // string
    extensions.push(autocompletion({ override: [stringAutoCode] }));
  }

  // 设置主题
  if (editorTheme.value) {
    extensions.push(editorTheme.value);
  }

  // 设置换行
  if (wrap.value) {
    extensions.push(EditorView.lineWrapping);
  }
  return extensions;
});

// 设置编辑器样式
const editorStyle = computed((): CSSProperties => {
  const heightValue = height.value;
  let h = '100vh';
  if (typeof heightValue === 'number') {
    h = `${heightValue}px`;
  } else {
    h = `${heightValue}`;
  }

  return {
    height: h,
    fontSize: `${fontSize.value}px`
  };
});

// 设置编辑器的语言
let editorLang = ref<any>('string');
watch(lang, (v) => {
  if (v === 'json') {
    editorLang.value = json();
  } else if (v === 'html') {
    editorLang.value = html();
  } else if (v === 'javascript') {
    editorLang.value = javascript();
  } else {
    editorLang.value = 'string';
  }
});

// 设置编辑器主题
let editorTheme = ref<any>();
if (dark.value) {
  editorTheme.value = oneDark;
}
watch(dark, (bool) => {
  if (bool) {
    editorTheme.value = oneDark;
  } else {
    editorTheme.value = null;
  }
});

// 设置编辑器内容
const editorValue = ref(value.value);
watch(value, (v) => {
  editorValue.value = v;
});
watch(editorValue, (v) => {
  emit('update:value', v);
});

watch(height, (v) => {
  let { from, to } = view.value.state.selection.main;
  view.value.dispatch({
    selection: { anchor: from, head: to },
    scrollIntoView: true
  });
});

// 打开关闭搜索面板
watch(searchPanel, (v) => {
  if (v) {
    openSearchPanel(view.value);
  } else {
    closeSearchPanel(view.value);
  }
});

// 滚动至xx位置
watch(scrollTo, (v: any) => {
  let pos = 0;
  const end = view.value.state.doc.length;
  if (typeof v === 'string') {
    if (v === 'start') {
      pos = 0;
    } else if (v === 'end') {
      pos = end;
    }
  }
  view.value.dispatch({
    effects: EditorView.scrollIntoView(pos, {
      y: 'start'
    })
  });
});

const onChange = (v: any) => {
  emit('change', v, view.value);
};
</script>

<style scoped lang="less">
.steve-editor {
  :deep(.cm-editor) {
    background: transparent !important;

    &.cm-focused {
      outline: none;
    }

    .cm-gutters {
      background: transparent !important;
    }

    .cm-statusbar {
      display: none;
    }

    .cm-tooltip {
      border: 0;
      border-radius: 5px;
    }

    .cm-panels {
      .cm-search {
      }
    }
  }
}
</style>
